package com.ruoyi.common.oss;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.*;
import com.ruoyi.common.utils.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 阿里云OSS上传工具类
 */
@Component
public class OSSUtils {

    private static final Logger log = LoggerFactory.getLogger(OSSUtils.class);

    @Autowired
    private OSSProperties ossProperties;

    private OSS ossClient; // OSS客户端（单例）

    // 初始化客户端（项目启动时执行）
    @PostConstruct
    public void init() {
        ossClient = new OSSClientBuilder().build(
                ossProperties.getEndpoint(),
                ossProperties.getAccessKeyId(),
                ossProperties.getAccessKeySecret()
        );
    }

    // 销毁客户端（项目关闭时执行）
    @PreDestroy
    public void destroy() {
        if (ossClient != null) {
            ossClient.shutdown();
        }
    }

    /**
     * 生成OSS文件路径（自动拼接基础路径+UUID避免重名）
     * @param originalFileName 原始文件名（用于获取后缀）
     * @return 完整OSS路径（如"upload/xxx.jpg"）
     */
    private String generateObjectName(String originalFileName) {
        // 提取文件后缀（如".jpg"）
        String suffix = "";
        if (originalFileName != null && originalFileName.contains(".")) {
            suffix = originalFileName.substring(originalFileName.lastIndexOf("."));
        }
        // 生成UUID文件名
        String fileName = UUID.randomUUID().toString() + suffix;
        // 拼接基础路径（若配置了basePath）
        String basePath = ossProperties.getBasePath();
        if (StringUtils.hasText(basePath)) {
            // 确保基础路径以"/"结尾（如"upload/"）
            if (!basePath.endsWith("/")) {
                basePath += "/";
            }
            return basePath + fileName;
        }
        return fileName;
    }

    /**
     * 上传本地文件
     * @param localFile 本地文件
     * @param originalFileName 原始文件名（用于生成路径）
     * @return 上传后的OSS文件URL（如"https://bucket.oss-cn-beijing.aliyuncs.com/upload/xxx.jpg"）
     */
    public String uploadFile(File localFile, String originalFileName) {
        return uploadFile(localFile, originalFileName, null, null);
    }

    /**
     * 上传本地文件（带权限和元数据）
     * @param localFile 本地文件
     * @param originalFileName 原始文件名
     * @param acl 文件权限（如CannedAccessControlList.PublicRead）
     * @param metadata 元数据（如Content-Type）
     * @return OSS文件URL
     */
    public String uploadFile(File localFile, String originalFileName,
                             CannedAccessControlList acl, ObjectMetadata metadata) {
        String objectName = generateObjectName(originalFileName);
        try {
            // 上传文件
            PutObjectRequest request = new PutObjectRequest(ossProperties.getBucketName(), objectName, localFile);
            if (metadata != null) {
                request.setMetadata(metadata);
            }
            ossClient.putObject(request);

            // 设置文件权限（如公开读）
            if (acl != null) {
                ossClient.setObjectAcl(ossProperties.getBucketName(), objectName, acl);
            }

            // 生成访问URL
            return "https://" + ossProperties.getBucketName() + "." + ossProperties.getEndpoint() + "/" + objectName;
        } catch (Exception e) {
            throw new RuntimeException("OSS文件上传失败：" + e.getMessage(), e);
        }
    }

    /**
     * 上传输入流（如网络流、内存流）
     * @param inputStream 输入流
     * @param originalFileName 原始文件名
     * @return OSS文件URL
     */
    public String uploadInputStream(InputStream inputStream, String originalFileName) {
        return uploadInputStream(inputStream, originalFileName, null, null);
    }

    /**
     * 上传输入流（带权限和元数据）
     * @param inputStream 输入流
     * @param originalFileName 原始文件名
     * @param acl 文件权限
     * @param metadata 元数据
     * @return OSS文件URL
     */
    public OSSFileRecord uploadInputStreamRecord(InputStream inputStream, String originalFileName,
                                    CannedAccessControlList acl, ObjectMetadata metadata) {
        String objectName = generateObjectName(originalFileName);
        try {
            PutObjectRequest request = new PutObjectRequest(ossProperties.getBucketName(), objectName, inputStream);
            if (metadata != null) {
                request.setMetadata(metadata);
            }
            ossClient.putObject(request);

            if (acl != null) {
                ossClient.setObjectAcl(ossProperties.getBucketName(), objectName, acl);
            }


            OSSFileRecord record = new OSSFileRecord();
            record.setObjectKey(objectName);
            record.setOriginalFileName(originalFileName);
            record.setUploadTime(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, new Date()));
            if (metadata != null) {
                record.setFileSize(String.valueOf(metadata.getContentLength()));
                record.setContentType(metadata.getContentType());
            }
            record.setAccessUrl("https://" + ossProperties.getBucketName() + "." + ossProperties.getEndpoint() + "/" + objectName);
            return record;
        } catch (Exception e) {
            throw new RuntimeException("OSS输入流上传失败：" + e.getMessage(), e);
        } finally {
            try {
                inputStream.close(); // 关闭流
            } catch (Exception ex) {
                // 忽略流关闭异常
            }
        }
    }


    public String uploadInputStream(InputStream inputStream, String originalFileName,
                                           CannedAccessControlList acl, ObjectMetadata metadata) {
        String objectName = generateObjectName(originalFileName);
        try {
            PutObjectRequest request = new PutObjectRequest(ossProperties.getBucketName(), objectName, inputStream);
            if (metadata != null) {
                request.setMetadata(metadata);
            }
            ossClient.putObject(request);

            if (acl != null) {
                ossClient.setObjectAcl(ossProperties.getBucketName(), objectName, acl);
            }
            return "https://" + ossProperties.getBucketName() + "." + ossProperties.getEndpoint() + "/" + objectName;
        } catch (Exception e) {
            throw new RuntimeException("OSS输入流上传失败：" + e.getMessage(), e);
        } finally {
            try {
                inputStream.close(); // 关闭流
            } catch (Exception ex) {
                // 忽略流关闭异常
            }
        }
    }

    /**
     * 上传字符串（文本内容）
     * @param content 字符串内容
     * @param originalFileName 原始文件名（如"test.txt"）
     * @return OSS文件URL
     */
    public String uploadString(String content, String originalFileName) {
        // 字符串转输入流（UTF-8编码）
        InputStream inputStream = new java.io.ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
        return uploadInputStream(inputStream, originalFileName);
    }

    /**
     * 删除OSS文件
     * @param objectName OSS文件路径（如"upload/xxx.jpg"）
     * @return 是否删除成功
     */
    public boolean deleteFile(String objectName) {
        try {
            ossClient.deleteObject(ossProperties.getBucketName(), objectName);
            return true;
        } catch (Exception e) {
            throw new RuntimeException("OSS文件删除失败：" + e.getMessage(), e);
        }
    }



    /**
     * 批量下载OSS文件并压缩成ZIP
     * @param objectKeys OSS文件路径列表
     * @param zipFileName 压缩包名称
     * @param response 响应对象
     */
    public void batchDownloadAndZip(List<String> objectKeys, String zipFileName, HttpServletResponse response) {
        // 设置响应头
        response.setContentType("application/zip");
        try {
            String encodedFileName = URLEncoder.encode(zipFileName, StandardCharsets.UTF_8.name());
            response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"");
        } catch (UnsupportedEncodingException e) {
            log.error("文件名编码失败", e);
            throw new RuntimeException("下载失败：文件名编码错误");
        }

        try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
            for (String objectKey : objectKeys) {
                // 跳过空路径
                if (StringUtils.isEmpty(objectKey)) {
                    continue;
                }
                // 检查文件是否存在
                if (!ossClient.doesObjectExist(ossProperties.getBucketName(), objectKey)) {
                    log.warn("文件不存在，跳过下载：{}", objectKey);
                    continue;
                }

                // 获取OSS文件并写入压缩包
                try (OSSObject ossObject = ossClient.getObject(ossProperties.getBucketName(), objectKey);
                     InputStream is = ossObject.getObjectContent()) {

                    // 提取文件名作为压缩包内文件名
                    String fileName = objectKey.substring(objectKey.lastIndexOf("/") + 1);
                    ZipEntry zipEntry = new ZipEntry(fileName);
                    zos.putNextEntry(zipEntry);

                    // 写入文件内容
                    byte[] buffer = new byte[1024 * 8];
                    int len;
                    while ((len = is.read(buffer)) != -1) {
                        zos.write(buffer, 0, len);
                    }
                    zos.closeEntry();
                    log.debug("文件已添加到压缩包：{}", objectKey);
                } catch (Exception e) {
                    log.error("处理文件失败：{}", objectKey, e);
                     throw new RuntimeException("处理文件[" + objectKey + "]失败", e);
                }
            }
            zos.finish();
            log.info("批量压缩下载完成，共处理{}个文件", objectKeys.size());
        } catch (Exception e) {
            log.error("压缩文件失败", e);
            throw new RuntimeException("下载失败：压缩文件错误");
        }
    }



    // ==================== 需在 OSSUtils 中新增的方法 ====================
    /**
     * OSSUtils 中新增：将指定OSS文件列表写入目标ZipOutputStream（供单个作品压缩使用）
     * @param objectKeys OSS文件路径列表
     * @param targetZipOs 目标ZipOutputStream（单个作品的压缩流）
     */

    public void writeWorkFilesToZip(List<String> objectKeys, ZipOutputStream targetZipOs) {
        try {
            for (String objectKey : objectKeys) {
                // 1. 校验OSS文件是否存在
                if (!ossClient.doesObjectExist(ossProperties.getBucketName(), objectKey)) {
                    log.warn("OSS文件不存在，跳过：{}", objectKey);
                    continue;
                }

                // 2. 获取OSS文件流并写入目标压缩流
                try (OSSObject ossObject = ossClient.getObject(ossProperties.getBucketName(), objectKey);
                     InputStream ossIs = ossObject.getObjectContent()) {

                    // 3. 提取文件名（作为压缩包内的文件名称）
                    String fileName = objectKey.substring(objectKey.lastIndexOf("/") + 1);
                    ZipEntry fileEntry = new ZipEntry(fileName);
                    targetZipOs.putNextEntry(fileEntry);

                    // 4. 写入文件内容
                    byte[] buffer = new byte[1024 * 8];
                    int len;
                    while ((len = ossIs.read(buffer)) != -1) {
                        targetZipOs.write(buffer, 0, len);
                    }
                    targetZipOs.closeEntry();
                    log.debug("OSS文件[{}]已写入压缩流", objectKey);
                }
            }
        } catch (Exception e) {
            log.error("将OSS文件写入压缩流失败", e);
            throw new RuntimeException("生成作品压缩包失败", e);
        }
    }



    /**
     * 分片上传输入流（带权限和元数据）
     * @param inputStream 输入流
     * @param originalFileName 原始文件名
     * @param acl 文件权限
     * @param metadata 元数据
     * @param partSize 分片大小（字节），默认5MB
     * @return OSS文件记录
     */
    public OSSFileRecord uploadInputStreamRecordMultipart(InputStream inputStream, String originalFileName,
                                                          CannedAccessControlList acl, ObjectMetadata metadata, Long partSize) {
        String objectName = generateObjectName(originalFileName);
        OSS client = ossClient;

        // 设置默认分片大小（5MB）
        if (partSize == null || partSize < 5 * 1024 * 1024) {
            partSize = 5L * 1024 * 1024;
        }

        InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest(
                ossProperties.getBucketName(), objectName);

        if (metadata != null) {
            initRequest.setObjectMetadata(metadata);
        }

        String uploadId = null;
        try {
            // 初始化分片上传
            InitiateMultipartUploadResult initResult = client.initiateMultipartUpload(initRequest);
            uploadId = initResult.getUploadId();

            // 上传分片
            List<PartETag> partETags = new ArrayList<>();
            byte[] buffer = new byte[partSize.intValue()];
            int bytesRead;
            int partNumber = 1;
            long totalSize = 0;

            while ((bytesRead = inputStream.read(buffer)) != -1) {
                // 创建上传分片请求
                UploadPartRequest uploadRequest = new UploadPartRequest();
                uploadRequest.setBucketName(ossProperties.getBucketName());
                uploadRequest.setKey(objectName);
                uploadRequest.setUploadId(uploadId);
                uploadRequest.setInputStream(new ByteArrayInputStream(buffer, 0, bytesRead));
                uploadRequest.setPartSize(bytesRead);
                uploadRequest.setPartNumber(partNumber);

                // 上传分片
                UploadPartResult uploadResult = client.uploadPart(uploadRequest);
                partETags.add(uploadResult.getPartETag());

                partNumber++;
                totalSize += bytesRead;
            }

            // 完成分片上传
            CompleteMultipartUploadRequest completeRequest =
                    new CompleteMultipartUploadRequest(ossProperties.getBucketName(), objectName, uploadId, partETags);
            client.completeMultipartUpload(completeRequest);

            // 设置权限
            if (acl != null) {
                client.setObjectAcl(ossProperties.getBucketName(), objectName, acl);
            }

            // 构建返回记录
            OSSFileRecord record = new OSSFileRecord();
            record.setObjectKey(objectName);
            record.setOriginalFileName(originalFileName);
            record.setUploadTime(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, new Date()));
            record.setFileSize(String.valueOf(totalSize));
            if (metadata != null && metadata.getContentType() != null) {
                record.setContentType(metadata.getContentType());
            }
            record.setAccessUrl("https://" + ossProperties.getBucketName() + "." + ossProperties.getEndpoint() + "/" + objectName);
            return record;

        } catch (Exception e) {
            e.printStackTrace();
            // 如果出现异常，中止分片上传
            if (uploadId != null) {
                client.abortMultipartUpload(new AbortMultipartUploadRequest(
                        ossProperties.getBucketName(), objectName, uploadId));
            }
            throw new RuntimeException("OSS分片上传失败：" + e.getMessage(), e);
        } finally {
            try {
                inputStream.close();
            } catch (Exception ex) {
                ex.printStackTrace();
                // 忽略流关闭异常
            }
        }
    }

    /**
     * 分片上传输入流（简化版本，使用默认分片大小）
     */
    public OSSFileRecord uploadInputStreamRecordMultipart(InputStream inputStream, String originalFileName,
                                                          CannedAccessControlList acl, ObjectMetadata metadata) {
        return uploadInputStreamRecordMultipart(inputStream, originalFileName, acl, metadata, null);
    }

    /**
     * 智能上传 - 根据文件大小自动选择上传方式
     * @param inputStream 输入流
     * @param originalFileName 原始文件名
     * @param acl 文件权限
     * @param metadata 元数据
     * @param threshold 阈值（字节），超过此大小使用分片上传，默认100MB
     * @return OSS文件记录
     */
    public OSSFileRecord uploadInputStreamSmart(InputStream inputStream, String originalFileName,
                                                CannedAccessControlList acl, ObjectMetadata metadata, Long threshold) {
        // 设置默认阈值（100MB）
        if (threshold == null) {
            threshold = 100L * 1024 * 1024;
        }

        try {
            // 对于无法获取大小的流，使用分片上传更安全
            if (inputStream instanceof ByteArrayInputStream ||
                    (metadata != null && metadata.getContentLength() > 0)) {
                long fileSize = metadata != null ? metadata.getContentLength() : 0;
                if (fileSize > threshold) {
                    return uploadInputStreamRecordMultipart(inputStream, originalFileName, acl, metadata);
                }
            }
            // 小文件使用普通上传
            return uploadInputStreamRecord(inputStream, originalFileName, acl, metadata);
        } catch (Exception e) {
            throw new RuntimeException("OSS文件上传失败：" + e.getMessage(), e);
        }
    }


}
